#include "ex03.cpp"
#include <stdint.h>
#include <stdio.h>
#include <unistd.h>

pw::semaphore ready(0), sem1(0), sem2(0), resource(1);
bool stop = false;


// [[ex03ga]]
// DEBUG descr The divided worker thread entrance in a split lock.
class splitlock
{
public:
	// DEBUG {
	// @param safe - flag: if true, place the lock initially into a safe state,
	//	 so that the worker thread may not enter it until the control
	//	 thread releases it; 
	//	 if false, place it into the unsafe state and allow the worker thread to 
	//	 enter it freely
	splitlock(bool safe = false) :
		cond_safe_(cond_enter_),
		may_enter_(!safe),
		is_safe_(safe)
	{ }

	// Control thread side of API.

	void request_safe()
	{
		pw::lockmutex lm(cond_enter_);
		may_enter_ = false;
	}

	void wait_safe()
	{
		pw::lockmutex lm(cond_enter_);
		while (!is_safe_) {
			cond_safe_.wait();
		}
	}

	// A convenience combination - just in case
	void lock_safe()
	{
		request_safe();
		wait_safe();
	}

	void release_safe()
	{
		pw::lockmutex lm(cond_enter_);
		may_enter_ = true;
		cond_enter_.broadcast();
	}

	// Worker thread side of API.

	void enter()
	{
		pw::lockmutex lm(cond_enter_);
		while (!may_enter_)
			cond_enter_.wait();
		is_safe_ = false;
	}

	void leave()
	{
		pw::lockmutex lm(cond_enter_);
		is_safe_ = true;
		cond_safe_.broadcast();
	}
	// DEBUG }
	// ...

	// Try to enter the critical section.  Never
	// blocks.  Returns true on success, false if the
	// splitlock is already safe.
	bool try_enter()
	{
		pw::lockmutex lm(cond_enter_);
		if (!may_enter_)
			return false; 
		is_safe_ = false;
		return true;
	}

	// After an enter attempt has failed and the
	// caller has freed all the sensitive resources,
	// block until the control thread releases the
	// lock and another attempt to enter may be made.
	void continue_enter()
	{
		pw::lockmutex lm(cond_enter_);
		while (!may_enter_)
			cond_enter_.wait();
	}

	// ...
	// DEBUG {
protected:
	pw::pmcond cond_enter_; // signaled when the worker thread is allowed to enter
	pw::pchaincond cond_safe_; // signaled when the control thread becomes safe
	bool may_enter_; // flag: the worker thread may enter
	bool is_safe_; // flag: the control thread is safe (working thread has not entered)
	// DEBUG }
};
// END

class ex03gb
{
public:
// [[ex03gb]]
// DEBUG descr A worker thread with resource allocation entering a split lock.
class worker : public pw::pwthread
{
public:
	worker() :
		split_(true)
	{ }
	// ...
	void *execute()
	{
		bool done = false;
		// ... initialize ...
	// DEBUG {
		ready.signal();
	// DEBUG }
		while(!done)
		{
			// ... get next request ...
			// ... get the resources
	// DEBUG {
			sem1.wait();
			resource.wait();
	// DEBUG }
			while (!split_.try_enter()) {
				// ... release the resources
	// DEBUG {
				resource.signal();
	// DEBUG }
				split_.continue_enter();
				// ... get the resources
	// DEBUG {
				resource.wait();
	// DEBUG }
			}
			// ... process the request ...
	// DEBUG {
			fprintf(stderr, "worker entered\n");
			sem2.wait();
			done = stop;
			fprintf(stderr, "worker leaving\n");
			resource.signal();
	// DEBUG }
			split_.leave();
			// ... release the resources
		}
		return 0;
	}

protected:
	// DEBUG {
public:
	// DEBUG }
	splitlock split_;
};
// END


// DEBUG {
static void main()
{
	stop = false;
	worker w;
	w.start();
	ready.wait();

	w.split_.lock_safe();
	fprintf(stderr, "locked safe\n");
	w.split_.release_safe();
	fprintf(stderr, "released safe\n");

	w.split_.request_safe();
	sem1.signal(2);
	sem2.signal(1);
	w.split_.wait_safe();
	fprintf(stderr, "locked safe\n");
	resource.wait();
	resource.signal();
	w.split_.release_safe();
	fprintf(stderr, "released safe\n");
	usleep(100*1000);
	
	w.split_.request_safe();
	sem2.signal(1);
	w.split_.wait_safe();
	fprintf(stderr, "locked safe\n");
	resource.wait();
	resource.signal();
	w.split_.release_safe();
	fprintf(stderr, "released safe\n");
	usleep(100*1000);

	stop = true;
	sem1.signal(1);
	sem2.signal(1);

	w.join();
}
// DEBUG }
};

class ex03gc
{
public:
// [[ex03gc]]
// DEBUG descr A worker thread that skips the critical section if the split lock is busy.
class worker : public pw::pwthread
{
public:
	worker() :
		split_(true)
	{ }
	// ...
	void *execute()
	{
		bool done = false;
	// DEBUG {
		ready.signal();
		int fn = 0;
	// DEBUG }
		// ... initialize ...
		while(!done)
		{
	// DEBUG {
			sem1.wait();
	// DEBUG }
			// ... wait for the next tick
			// ... advance to the next frame
			if (!split_.try_enter()) {
	// DEBUG {
				fprintf(stderr, " worker skipped frame %d\n", ++fn);
				sem2.wait();
	// DEBUG }
				continue; // try the next tick
			}
	// DEBUG {
			fprintf(stderr, " worker entered frame %d\n", ++fn);
			sem2.wait();
			done = stop;
			fprintf(stderr, " worker leaving\n");
			resource.signal();
	// DEBUG }
			// ... draw the frame
			split_.leave();
		}
	// DEBUG {
		fprintf(stderr, " worker stopped\n");
	// DEBUG }
		return 0;
	}

protected:
	// DEBUG {
public:
	// DEBUG }
	splitlock split_;
};
// END

// DEBUG {
static void main()
{
	stop = false;
	worker w;
	w.start();
	ready.wait();

	w.split_.lock_safe();
	fprintf(stderr, "locked safe\n");
	w.split_.release_safe();
	fprintf(stderr, "released safe\n");

	w.split_.request_safe();
	sem1.signal(2);
	sem2.signal(1);
	usleep(100*1000);
	w.split_.wait_safe();
	fprintf(stderr, "locked safe\n");
	w.split_.release_safe();
	fprintf(stderr, "released safe\n");
	usleep(100*1000);
	
	w.split_.request_safe();
	sem2.signal(1);
	w.split_.wait_safe();
	fprintf(stderr, "locked safe\n");
	resource.wait();
	resource.signal();
	w.split_.release_safe();
	fprintf(stderr, "released safe\n");
	usleep(100*1000);

	stop = true;
	sem1.signal(1);
	sem2.signal(1);

	w.join();
}
// DEBUG }
};

class ex03gd
{
public:
static const int64_t FRAME_PERIOD=100000000;
// [[ex03gd]]
// DEBUG descr A worker thread processing the time events with a split lock.
class worker : public pw::pwthread
{
public:
	worker() :
		split_(true)
	{ }
	// ...
	void *execute()
	{
		bool done = false;
		// ... initialize ...
		// time for the next frame
		struct timespec absnext; 
		// the current time
		clock_gettime(CLOCK_REALTIME, &absnext); 
	// DEBUG {
		ready.signal();
		int fn = 0;
		struct timespec abslast; // last time of animation
		abslast = absnext; // start time of animation
	// DEBUG }
		while(!done)
		{
			timer_.timedwait(absnext);
	// DEBUG {
			fprintf(stderr, " worker timer triggered\n");
			sem1.wait();
	// DEBUG }

			split_.enter();

			// the current time
			clock_gettime(CLOCK_REALTIME, &absnext); 
			// ... draw the frame of the animation
			// matching the time
	// DEBUG {
			++fn;
			int skip = ((int64_t)absnext.tv_sec*1000000000+absnext.tv_nsec
				- ((int64_t)abslast.tv_sec*1000000000+abslast.tv_nsec))/FRAME_PERIOD;
			if (skip > 1) // Linux wakes up a little too early, so normally it's 0
				fn += skip-1;
			fprintf(stderr, " worker entered frame %d\n", fn);
			sem2.wait();
			done = stop;
			fprintf(stderr, " worker leaving\n");
			resource.signal();
			abslast = absnext; // last time of animation
	// DEBUG }

			split_.leave();

			// advance to the next frame,
			// the frame rate may drift a little
			absnext.tv_nsec += FRAME_PERIOD;
			if (absnext.tv_nsec > 1000*1000*1000) {
				absnext.tv_nsec -= 1000*1000*1000;
				absnext.tv_sec += 1;
			}
		}
	// DEBUG {
		fprintf(stderr, " worker stopped\n");
	// DEBUG }
		return 0;
	}

protected:
	// DEBUG {
public:
	// DEBUG }
	splitlock split_;
	// never signaled, used for timed waits
	pw::pmcond timer_; 
};
// END
// DEBUG {
static void main()
{
	stop = false;
	worker w;
	w.start();
	ready.wait();

	w.split_.lock_safe();
	fprintf(stderr, "locked safe\n");
	w.split_.release_safe();
	fprintf(stderr, "released safe\n");

	w.split_.request_safe();
	sem1.signal(2);
	sem2.signal(1);
	usleep(220*1000);
	w.split_.wait_safe();
	fprintf(stderr, "locked safe\n");
	w.split_.release_safe();
	fprintf(stderr, "released safe\n");
	usleep(50*1000);
	
	w.split_.request_safe();
	sem2.signal(1);
	usleep(220*1000);
	w.split_.wait_safe();
	fprintf(stderr, "locked safe\n");
	resource.wait();
	resource.signal();
	w.split_.release_safe();
	fprintf(stderr, "released safe\n");
	usleep(220*1000);

	stop = true;
	sem1.signal(1);
	sem2.signal(1);

	w.join();
}
// DEBUG }
};

void ex03ge()
{
class workerClass : public pw::pwthread
{
public:
	splitlock split_;
	pw::pmcond timer_; // never signaled, used for timed waits
};
workerClass *worker_ptr;
// [[ex03ge]]
worker_ptr->split_.release_safe();
worker_ptr->split_.lock_safe();
// END
}

// DEBUG {
int main()
{
	fprintf(stderr, "--- ex03gb\n");
	ex03gb::main();
	fprintf(stderr, "--- ex03gc\n");
	ex03gc::main();
	fprintf(stderr, "--- ex03gd\n");
	ex03gd::main();
	return 0;
}
// DEBUG }
/* Sample output:

--- ex03gb
locked safe
released safe
locked safe
released safe
worker entered
worker leaving
worker entered
worker leaving
locked safe
released safe
worker entered
worker leaving
--- ex03gc
locked safe
released safe
 worker skipped frame 1
 worker skipped frame 2
locked safe
released safe
locked safe
released safe
 worker entered frame 3
 worker leaving
 worker stopped
--- ex03gd
 worker timer triggered
locked safe
released safe
locked safe
released safe
 worker entered frame 2
 worker leaving
 worker timer triggered
locked safe
released safe
 worker entered frame 4
 worker leaving
 worker timer triggered
 worker entered frame 6
 worker leaving
 worker stopped

*/
